#ifndef PORT_H
#define PORT_H

#include <mutex>

#include <QList>

#include "base.h"

template<class T>
class PortIn : public JObject
{
public:
    PortIn(JObject *parent = nullptr, int max_size = -1, int almost_full_size = -1) :
        JObject(parent),
        __max_usage(0),
        __max_size(-1),
        __almost_full_size(-1)
    {
        set_max_size(max_size);
        set_almost_full_size(almost_full_size);
    }

    virtual ~PortIn()
    {
    }

    virtual T current() const
    {
        return __current;
    }

    virtual int size() const
    {
        return __buffer.size();
    }

    virtual int max_size() const
    {
        return __max_size;
    }

    virtual void set_max_size(int max_size)
    {
        __max_size = max_size;
    }

    virtual void set_almost_full_size(int almost_full)
    {
        __almost_full_size = (almost_full == -1 && __max_size > 0) ?
                    __max_size - 2 : almost_full;
    }

    virtual bool is_full() const
    {
        return __max_size > 0 && size() >= max_size();
    }

    virtual bool is_empty() const
    {
        return size() == 0;
    }

    virtual T head() const
    {
        return __buffer.first();
    }

    virtual void put(const T &value)
    {
        //Lock the port.
        __lock.lock();
        //Append the value.
        __buffer.append(value);
        //Increase the usage.
        __max_usage = qMax(__max_usage, size());
        // Release the lock.
        __lock.unlock();
    }

    virtual T get()
    {
        //Lock the port.
        __lock.lock();
        //Pop the data to current.
        __current = __buffer.takeFirst();
        // Release the lock.
        __lock.unlock();
        //Give the current item back.
        return __current;
    }

    virtual int max_history_usage() const
    {
        return __max_usage;
    }

    virtual int current_usage() const
    {
        return size();
    }

private:
    // Create the buffer and current item holder.
    QList<T> __buffer;
    T __current;
    // Prepare variables.
    int __max_usage, __max_size, __almost_full_size;
    // Prepare for multi-threading lock.
    std::mutex __lock;
};

template<class T>
class PortInDirect : public PortIn<T>
{
public:
    PortInDirect(JObject *parent = nullptr) :
        PortIn<T>(parent, 1, 1)
    {
    }

    ~PortInDirect() override
    {
    }

    T current() const override
    {
        return __buffer;
    }

    int size() const override
    {
        return 1;
    }

    int max_size() const override
    {
        return 1;
    }

    void set_max_size(int max_size) override
    {
        Q_UNUSED(max_size)
    }

    void set_almost_full_size(int almost_full) override
    {
        Q_UNUSED(almost_full)
    }

    bool is_full() const override
    {
        return false;
    }

    bool is_empty() const override
    {
        return false;
    }

    T head() const override
    {
        return __buffer;
    }

    void put(const T &value) override
    {
        //Append the value.
        __buffer = value;
    }

    T get() override
    {
        return __buffer;
    }

    int max_history_usage() const override
    {
        return 1;
    }

    int current_usage() const override
    {
        return 1;
    }

private:
    T __buffer;
};

template<class T>
class PortOut : public JObject
{
public:
    PortOut(JObject *parent = nullptr) :
        JObject(parent),
        __port_in(nullptr)
    {
    }

    T current() const
    {
        return __current;
    }

    void connect(PortIn<T> *port)
    {
        // Check current pointer.
        assert(__port_in == nullptr);
        // Save the port.
        __port_in = port;
    }

    void put(const T &value)
    {
        //Save to current.
        __current = value;
        // If the port is connected, put the data to port.
        if(!is_no_connection())
        {
            __port_in->put(value);
        }
    }

    bool is_no_connection() const
    {
        return __port_in == nullptr;
    }

    bool is_full() const
    {
        return is_no_connection() ? false : __port_in->is_full();
    }

    PortIn<T> *connected_port() const
    {
        return __port_in;
    }

private:
    PortIn<T> *__port_in;
    T __current;
};

#endif // PORT_H
